//#define USE_DUNGEONBUDDY_DLL

using System;
using System.Collections.Generic;
using System.Linq;
using Bots.DungeonBuddy.Behaviors;
using CommonBehaviors.Actions;
using Styx;
using Styx.Common;
using Styx.Common.Helpers;
using Styx.CommonBot;
using Styx.CommonBot.Frames;
using Styx.Helpers;
using Styx.Patchables;
using Styx.Pathing;
using Styx.TreeSharp;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;
using Styx.WoWInternals.World;
using Tripper.Tools.Math;
using Action = Styx.TreeSharp.Action;
#if USE_DUNGEONBUDDY_DLL
using Bots.DungeonBuddyDll;
using Bots.DungeonBuddyDll.Profiles;
using Bots.DungeonBuddyDll.Attributes;
using Bots.DungeonBuddyDll.Helpers;
using AvoidanceManager = Bots.DungeonBuddyDll.Avoidance.AvoidanceManager;

namespace Bots.DungeonBuddyDll.Dungeon_Scripts.Mists_of_Pandaria
#else
    using Bots.DungeonBuddy.Profiles;
    using Bots.DungeonBuddy.Attributes;
    using Bots.DungeonBuddy.Helpers;
    using AvoidanceManager = Bots.DungeonBuddy.Avoidance.AvoidanceManager;
namespace Bots.DungeonBuddy.Dungeon_Scripts.Mists_of_Pandaria
#endif
{

    #region Normal Difficulty

    public class GateOfTheSettingSun : Dungeon
    {
        #region Overrides of Dungeon

        private const float ElevatorBottomZ = 388.1946f;
        private const float ElevatorTopZ = 430.2219f;
        private const float SouthOfGreatDoorX = 917;
        private const float NorthOfGreatDoorX = 1000;
        private const float LevelDividerZ = 350;
        private const uint RopeStalkerId = 58109;
        private const uint DoodadVebGreatDoorPhase001Id = 211245;
        private readonly WoWPoint _elevatorBottomBoardLoc = new WoWPoint(1195.256, 2301.015, 388.8313);
        //private readonly WoWPoint _elevatorBottomWaitLoc = new WoWPoint(1192.767, 2296.851, 388.1232);
        private readonly WoWPoint _elevatorBottomWaitLoc = new WoWPoint(1193.303, 2297.308, 388.1255);
        private readonly WoWPoint _elevatorTopBoardLoc = new WoWPoint(1195.167, 2304.742, 430.8589);
        private readonly WoWPoint _elevatorTopWaitLoc = new WoWPoint(1203.566, 2304.513, 430.8623);
        private readonly WoWPoint _gadokRoomLoc = new WoWPoint(1195.002, 2305.316, 430.8587);

        private readonly CircularQueue<WoWPoint> _ropeLocations = new CircularQueue<WoWPoint>
                                                                  {
                                                                      new WoWPoint(842.0536, 2315.725, 381.3216),
                                                                      new WoWPoint(842.3929, 2299.741, 381.3215)
                                                                  };

        public override bool IsFlyingCorpseRun
        {
            get
            {
                return true;
            }
        }

        private WaitTimer _ropeTimer;
        private WaitTimer _ropeInteractTimer = new WaitTimer(TimeSpan.FromSeconds(2));
        private uint _ropeUseCounter;

        public override uint DungeonId
        {
            get { return 631; }
        }

        public override WoWPoint Entrance
        {
            get { return new WoWPoint(699.101, 2080.291, 374.6979); }
        }

        private bool GreatDoorBroken
        {
            get
            {
                var door = ObjectManager.GetObjectsOfType<WoWGameObject>().FirstOrDefault(g => g.Entry == DoodadVebGreatDoorPhase001Id);
                return door != null && door.State == WoWGameObjectState.Ready;
            }
        }

        public override void OnEnter()
        {
            _wallScalingPackTimer = null;
        }

        public override void RemoveTargetsFilter(List<WoWObject> units)
        {
            units.RemoveAll(
                ret =>
                {
                    var unit = ret.ToUnit();
                    if (unit != null)
                    {
                        if (_inCombatMobs.Contains(unit.Entry) && unit.Combat && Me.Combat && !unit.IsTargetingMeOrPet && !unit.IsTargetingMyPartyMember &&
                            !unit.TaggedByMe)
                            return true;
                        if (unit.Entry == KrikthikEngulferId && (Me.IsMelee() || unit.Distance > 35 || Me.HasAura("")))
                            return true;
                        // ignore Striker Gadok if he's on a strafing run  (unless you're healer since some healer routines won't heal if theres nothing it Targeting)
                        if (unit.Entry == StrikerGadokId && unit.Z > StrikerGadokStrafeZ && !Me.IsHealer())
                            return true;
                        // force tank to get off elevator before calling CR pull behavior and getting stuck due to CR blacklisting because boss can't be navigated to from elevator.
                        if (unit.Entry == StrikerGadokId && Me.IsTank() && Me.TransportGuid != 0 && !Me.Combat)
                            return true;

                        if (unit.Entry == KrikthikSwarmerId && (!Me.IsTank() || !_pickupSwarmers))
                            return true;

                        if (unit.Entry == RaigonnId && unit.Combat && unit.HasAura("Impervious Carapace") && Me.IsDps())
                            return true;
                    }
                    return false;
                });
        }

        public override void IncludeTargetsFilter(List<WoWObject> incomingunits, HashSet<WoWObject> outgoingunits)
        {
            foreach (var obj in incomingunits)
            {
                var unit = obj as WoWUnit;
                if (unit != null)
                {
                    // I have to add these units manually because they apear as non-hostile if using WoWUnit.IsHostile.
                    if (_inCombatMobs.Contains(unit.Entry) && !Me.Combat && unit.Distance <= 40)
                        outgoingunits.Add(unit);
                    if (unit.Entry == RaigonnId && !unit.Combat && Me.IsTank() && unit.Distance < 40)
                        outgoingunits.Add(unit);
                    if (unit.Entry == WeakSpotId && Me.HasAura(FactionOverrideId))
                        outgoingunits.Add(unit);
                    if (unit.Entry == KrikthikEngulferId && Me.IsRange())
                        outgoingunits.Add(unit);
                }
            }
        }

        public override void WeighTargetsFilter(List<Targeting.TargetPriority> units)
        {
            foreach (var priority in units)
            {
                var unit = priority.Object as WoWUnit;
                if (unit != null)
                {
                    if (unit.Entry == WeakSpotId)
                        priority.Score += 5000;

                    // ignore the adds on commander Rimok if I'm dps.
                    if (unit.Entry == CommanderRimokId && Me.IsDps())
                        priority.Score += 5000;

                    if (unit.Entry == CommanderRimokId && Me.IsTank() && unit.CurrentTargetGuid != Me.Guid)
                        priority.Score += 5000;

                    if (unit.Entry == KrikthikEngulferId && Me.IsRange())
                        priority.Score += 3000;
                }
            }
        }

        public override MoveResult MoveTo(WoWPoint location)
        {
            if (ElevatorBehavior(location))
                return MoveResult.Moved;

            var myLoc = Me.Location;
            var iAmNorthOfGreatDoor = myLoc.X > NorthOfGreatDoorX;
            var iAmSouthOfGreatDoor = myLoc.X < SouthOfGreatDoorX;
            var destinationIsNorthOfGreatDoor = location.X > NorthOfGreatDoorX;
            var destinationIsSouthOfGreatDoor = location.X < SouthOfGreatDoorX;
            var iAmOnBottomLevel = myLoc.Z < LevelDividerZ;
            var destinationIsOnBottomLevel = location.Z < LevelDividerZ;

            var movingNorthAcrossGreatDoor = (iAmSouthOfGreatDoor || iAmOnBottomLevel) && destinationIsNorthOfGreatDoor && !destinationIsOnBottomLevel;
            var movingSouthAcrossGreatDoor = (iAmNorthOfGreatDoor || iAmOnBottomLevel) && destinationIsSouthOfGreatDoor && !destinationIsOnBottomLevel;
            var movingToBottomLevel = !iAmOnBottomLevel && destinationIsOnBottomLevel;
            var movingToTopLevel = iAmOnBottomLevel && !destinationIsOnBottomLevel;

            // are we moving to last boss or do we need to get to other side of broken door?
            if ((movingToBottomLevel || ((movingNorthAcrossGreatDoor || movingSouthAcrossGreatDoor) && !iAmOnBottomLevel && GreatDoorBroken)) && Me.TransportGuid == 0)
            {
                // use the ropes to get down
                if (iAmSouthOfGreatDoor)
                {
                    var ropeLoc = _ropeLocations.Peek();

                    if (myLoc.Distance(ropeLoc) > 20 && _ropeUseCounter > 0)
                        _ropeUseCounter = 0;

                    if (myLoc.Distance(ropeLoc) > 4)
                        return Navigator.MoveTo(ropeLoc);
                    if (myLoc.DistanceSqr(ropeLoc) > 2)
                        Navigator.PlayerMover.MoveTowards(ropeLoc);
                    else
                    {
                        if (Me.Mounted)
                            Mount.Dismount("Using rope");
                        WoWUnit _rope = ObjectManager.GetObjectsOfType<WoWUnit>().Where(u => u.Entry == RopeId).OrderBy(r => r.Location.DistanceSqr(ropeLoc)).FirstOrDefault();
                        if (_rope != null && _rope.IsValid)
                        {
                            if (_ropeTimer == null)
                            {
                                _ropeTimer = new WaitTimer(TimeSpan.FromMilliseconds(ScriptHelpers.Rnd.Next(10, 6000)));
                                _ropeTimer.Reset();
                            }
                            var otherGroupMemberIsUsingRope =
                                ScriptHelpers.GroupMembers.Any(
                                    g => g.Guid != Me.Guid && g.Player != null && g.Player.TransportGuid != 0 && g.Player.Transport.Entry == RopeStalkerId);
                            var yieldToOther =
                                ScriptHelpers.GroupMembers.Any(g => g.Guid != Me.Guid && g.Location.Distance(_rope.Location) < Me.Location.Distance(_rope.Location)) &&
                                !_ropeTimer.IsFinished;
                            if (!otherGroupMemberIsUsingRope && !yieldToOther && _ropeInteractTimer.IsFinished)
                            {
                                if (_ropeUseCounter < 20)
                                {
                                    _rope.Interact();
                                    _ropeInteractTimer.Reset();
                                    _ropeUseCounter++;
                                    _ropeTimer = null;
                                }
                                else
                                {
                                    Lua.DoString("LeaveParty()");
                                    Logger.WriteDebug("Ropes are bugged. Leaving group");
                                }
                                _ropeLocations.Dequeue();
                            }
                        }
                    }
                }
                // jump down a hole through floor to get down from north side.
                else
                {
                    if (myLoc.Z > 375)
                    {
                        if (myLoc.Distance(_northTopToBottomStepOnePoint) > 15)
                            return Navigator.MoveTo(_northTopToBottomStepOnePoint);
                        Navigator.PlayerMover.MoveTowards(_northTopToBottomStepTwoPoint);
                    }
                    else if (myLoc.Z > 350)
                    {
                        Navigator.PlayerMover.MoveTowards(_northTopToBottomStepThreePoint);
                    }
                }
                return MoveResult.Moved;
            }
            // are we at last boss and we want to move to the walls?
            if (movingToTopLevel)
            {
                var artilleryLoc = movingNorthAcrossGreatDoor ? _artilleryNorthLoc : _artillerySouthLoc;
                if (myLoc.Distance(artilleryLoc) > 4)
                    return Navigator.MoveTo(artilleryLoc);
                if (Me.Mounted)
                    Mount.Dismount("Using Artillery");
                var artillery = ObjectManager.GetObjectsOfType<WoWUnit>().OrderBy(u => u.DistanceSqr).FirstOrDefault(u => u.Entry == ArtilleryId);
                if (artillery != null)
                    artillery.Interact();
                return MoveResult.Moved;
            }
            return MoveResult.Failed;
        }


        [EncounterHandler(64710, "Rope", Mode = CallBehaviorMode.Proximity, BossRange = 10000)]
        public Composite RopeEncounter()
        {
            return new PrioritySelector(
                new Decorator(ctx => ScriptHelpers.IsBossAlive("Commander Ri'mok"), new Action(ctx => ScriptHelpers.MarkBossAsDead("Commander Ri'mok"))));
        }

        #endregion

        private const uint KrikthikInfiltratorId = 56890;
        private const uint KrikthikInfiltrator2Id = 58108;
        private const uint KrikthikWindShaperId = 59801;
        private const uint KrikthikRagerId = 59800;
        private const uint VolatileMunitionsId = 56896;
        private const uint StableMunitionsId = 56917;
        private const uint LeverId = 211284;
        private const int NorthSouthStrafingRunId = 107342;
        private const int EastWeastStrafingRunId = 107284;
        private const uint StrikerGadokId = 56589;
        private const float StrikerGadokStrafeZ = 432;
        private const uint KrikthikBombardierId = 56706;
        private const int FrenziedAssaultId = 107120;
        private const uint CommanderRimokId = 56636;
        private const uint KrikthikSwarmerId = 59835;
        private const uint RopeId = 64710;
        private const uint ArtilleryId = 66904;
        private const int ImperviousCarapaceId = 107118;
        private const uint RaigonnId = 56877;
        private const uint WeakSpotId = 56895;
        private const int FactionOverrideId = 107132;
        const uint KrikthikEngulferId = 56912;

        private const uint ElevatorId = 211013;
        private readonly WoWPoint _artillerySouthLoc = new WoWPoint(866.2046, 2225.777, 311.2762);

        private readonly WoWPoint _eastPoint = new WoWPoint(1195.388, 2275.083, 430.8744);
        private readonly uint[] _inCombatMobs = new[] { KrikthikInfiltratorId, KrikthikInfiltrator2Id, KrikthikWindShaperId, KrikthikRagerId };

        private readonly uint[] _mantidMunitionsIds = new uint[] { 56911, 56918, 56919, 56920, 59205, 59206, 59207, 59208 };

        private readonly WoWPoint _northPoint = new WoWPoint(1223.854, 2304.638, 430.874);
        private readonly WoWPoint _northTopToBottomStepOnePoint = new WoWPoint(1096.306, 2309.375, 381.5589);
        private readonly WoWPoint _northTopToBottomStepThreePoint = new WoWPoint(1093.083, 2311.584, 330.8067);
        private readonly WoWPoint _northTopToBottomStepTwoPoint = new WoWPoint(1081.378, 2299.337, 364.1641);
        private readonly WoWPoint _southPoint = new WoWPoint(1166.775, 2304.836, 430.874);
        private readonly WoWPoint _westPoint = new WoWPoint(1194.746, 2333.279, 430.874);
        private WoWPoint _artilleryNorthLoc = new WoWPoint(1051.559, 2224.952, 311.2657);
        private bool _pickupSwarmers;
        private WaitTimer _wallScalingPackTimer = new WaitTimer(TimeSpan.FromSeconds(10));

        private static LocalPlayer Me
        {
            get { return StyxWoW.Me; }
        }

        [EncounterHandler(64467, "Bowmistress Li", Mode = CallBehaviorMode.Proximity)]
        public Composite BowmistressLiEncounter()
        {
            return new PrioritySelector(
                new Decorator<WoWUnit>(u => u.QuestGiverStatus == QuestGiverStatus.Available, ScriptHelpers.CreatePickupQuest(64467)),
                new Decorator<WoWUnit>(u => u.QuestGiverStatus == QuestGiverStatus.TurnIn, ScriptHelpers.CreateTurninQuest(64467)));
        }

        [EncounterHandler(0)]
        public Composite RootEncounter()
        {
            AddAvoidObject(ctx => true, 5, VolatileMunitionsId);
            // AddAvoidObject(ctx => Me.TransportGuid == 0, 2f, LeverId);
            // run from the Munitations explosions.
            AddAvoidObject(
                ctx => true,
                3,
                u => _mantidMunitionsIds.Contains(u.Entry),
                u =>
                {
                    var start = u.Location;
                    return Me.Location.GetNearestPointOnSegment(start, start.RayCast(WoWMathHelper.NormalizeRadian(u.Rotation), 20));
                });
            return new PrioritySelector(CreateFollowerElevatorBehavior());
        }

        [EncounterHandler(56906, "Saboteur Kip'tilak", Mode = CallBehaviorMode.Proximity, BossRange = 100)]
        public Composite SaboteurKiptilakEncounter()
        {
            WoWUnit boss = null;
            var insideDoorLoc = new WoWPoint(722.3629, 2281.56, 387.9894);
            AddAvoidObject(
                ctx => Me.RaidMembers.Any(u => u.HasAura("Sabotage")),
                8,
                u =>
                u.Entry == StableMunitionsId &&
                Me.RaidMembers.Any(p => p.HasAura("Sabotage") && (Math.Abs(u.X - p.X) < 4 || Math.Abs(u.Y - p.Y) < 4) && u.Location.Distance(p.Location) < 20));
            AddAvoidObject(ctx => true, 8, u => u is WoWPlayer && u != Me && u.ToPlayer().HasAura("Sabotage"));
            // run away from any stable munitions that are aligned north/south or east/west with a group member with Sabotage debuf.
            AddAvoidObject(ctx => Me.HasAura("Sabotage"), 8, u => u is WoWPlayer && u != Me);
            return new PrioritySelector(
                ctx => boss = ctx as WoWUnit,
                new Decorator(
                    ctx => Me.IsTank() && !Me.Combat && Me.Y >= 2270 && !ScriptHelpers.GroupMembers.All(g => g.Location.Y >= 2270),
                    new PrioritySelector(new ActionSetActivity("Waiting for group members to get inside room"), new ActionAlwaysSucceed())),
                // get foot inside room before door closes
                new Decorator(ctx => Me.Y < 2281 && Targeting.Instance.IsEmpty(), new Action(ctx => Navigator.MoveTo(insideDoorLoc))));
        }

        [LocationHandler(830.1323f, 2312.181f, 381.2352f, 20, "Wait For wall scaling pack")]
        public Composite WaitForWallScalingPack()
        {
            return new PrioritySelector(
                new Decorator(
                    ctx => _wallScalingPackTimer == null,
                    new Action(
                        ctx =>
                        {
                            _wallScalingPackTimer = new WaitTimer(TimeSpan.FromSeconds(10));
                            _wallScalingPackTimer.Reset();
                        })),
                new Decorator(
                    ctx => !_wallScalingPackTimer.IsFinished && Me.IsTank() && Targeting.Instance.IsEmpty(),
                    new PrioritySelector(new Decorator(ctx => Me.IsMoving, new Action(ctx => WoWMovement.MoveStop())), new ActionAlwaysSucceed())));
        }

        [EncounterHandler(56589, "Striker Ga'dok", BossRange = 300)]
        public Composite StrikerGadokEncounter()
        {
            const uint acidBombId = 59813;

            WoWPoint nearestAvoidStrafePoint = WoWPoint.Zero;
            AddAvoidLocation(ctx => true, 5, m => ((WoWMissile)m).ImpactPosition, () => WoWMissile.InFlightMissiles.Where(m => m.SpellId == acidBombId));
            AddAvoidObject(ctx => true, 5, acidBombId);

            WoWUnit boss = null;
            return new PrioritySelector(
                ctx => boss = ctx as WoWUnit,
                // strafe run.
                new Decorator(
                    ctx => boss.Z > StrikerGadokStrafeZ && (Me.IsTank() && Targeting.Instance.TargetList.All(t => t.IsTargetingMeOrPet) || !Me.IsTank()),
                    new PrioritySelector(
                        ctx => nearestAvoidStrafePoint = GetNearestAvoidStrafePoint(boss),
                        new Decorator(
                            ctx => Me.Location.Distance(nearestAvoidStrafePoint) > 10,
                            new Action(ctx => Navigator.GetRunStatusFromMoveResult(Navigator.MoveTo(nearestAvoidStrafePoint)))),
                // don't go anywhere.. 
                        new Decorator(ctx => Targeting.Instance.IsEmpty() && Me.IsTank(), new ActionAlwaysSucceed()))));
        }

        private WoWPoint GetNearestAvoidStrafePoint(WoWUnit gadok)
        {
            var tank = ScriptHelpers.Tank;
            var myLoc = tank != null && tank != Me ? tank.Location : Me.Location;
            var bossLoc = gadok.Location;

            var bossToNorthSouthLineDistanceSqr = bossLoc.GetNearestPointOnLine(_northPoint, _southPoint).DistanceSqr(bossLoc);
            var bossToEastWestLineDistanceSqr = bossLoc.GetNearestPointOnLine(_eastPoint, _westPoint).DistanceSqr(bossLoc);

            if (bossToEastWestLineDistanceSqr < bossToNorthSouthLineDistanceSqr)
            {
                TreeRoot.StatusText = "Avoiding East/West Strafing run";
                return myLoc.DistanceSqr(_northPoint) < myLoc.DistanceSqr(_southPoint) ? _northPoint : _southPoint;
            }
            TreeRoot.StatusText = "Avoiding North/South Strafing run";
            return myLoc.DistanceSqr(_eastPoint) < myLoc.DistanceSqr(_westPoint) ? _eastPoint : _westPoint;
        }

        [EncounterHandler(60415, "Flak Cannon", Mode = CallBehaviorMode.Proximity, BossRange = 100)]
        public Composite FlakCannonEncounter()
        {
            WoWUnit boss = null;
            const int lootSparkleId = 92406;
            return new PrioritySelector(
                ctx => boss = ctx as WoWUnit,
                new Decorator(
                    ctx => boss.HasAura(lootSparkleId) && ObjectManager.GetObjectsOfType<WoWUnit>().Any(u => u.Entry == KrikthikBombardierId && u.IsAlive),
                    ScriptHelpers.CreateInteractWithObject(() => boss)));
        }

        [ObjectHandler(211129, "Signal Flame", ObjectRange = 250)]
        public Composite SignalFlameHandler()
        {
            var signalLocation = new WoWPoint(1372.582, 2282.429, 401.3846);
            return new PrioritySelector(
                new Decorator<WoWGameObject>(flame => flame.State == WoWGameObjectState.Active && !Me.Combat && flame.CanUse(),
                    new PrioritySelector(
                        new Decorator(ctx => ScriptHelpers.IsBossAlive("Saboteur Kip'tilak"), new Action(ctx => ScriptHelpers.MarkBossAsDead("Saboteur Kip'tilak"))),
                        new Decorator(ctx => ScriptHelpers.IsBossAlive("Striker Ga'dok"), new Action(ctx => ScriptHelpers.MarkBossAsDead("Striker Ga'dok"))),
                        new Decorator<WoWGameObject>(flame => Me.IsTank() && flame.Distance >= 50, new Action(ctx => ScriptHelpers.MoveTankTo(signalLocation))),
                        new Decorator<WoWGameObject>(
                            flame => Me.IsTank() && flame.Distance < 50,
                            new PrioritySelector(
                                new Decorator<WoWGameObject>(flame => !flame.CanUseNow(), new Action(ctx => Navigator.MoveTo(signalLocation))),
                                new Decorator<WoWGameObject>(flame => flame.CanUseNow(), new Helpers.Action<WoWGameObject>(flame => flame.Interact())))))));
        }

        [EncounterHandler(56636, "Commander Ri'mok")]
        public Composite CommanderRimokEncounter()
        {
            const uint viscosFluidStalkerId = 56883;
            const int frenziedAssaultId = 107120;
            var kiteCenterLoc = new WoWPoint(1297.094, 2301.952, 388.9373);
            WoWUnit boss = null;
            WoWPoint tankStepOutOfMeleeRangePoint = WoWPoint.Zero;

            AddAvoidObject(
                ctx => StyxWoW.Me.IsTank() && !_pickupSwarmers && boss != null && boss.IsValid && boss.CurrentTargetGuid == Me.Guid,
                () => kiteCenterLoc,
                46,
                17,
                u =>
                u.Entry == viscosFluidStalkerId && boss != null && boss.IsValid && boss.Location.DistanceSqr(u.Location) < 4 && boss.HasAura("Viscous Fluid") &&
                boss.Auras["Viscous Fluid"].StackCount >= 3);

            AddAvoidObject(ctx => Me.IsDps() && Me.IsRange() && !(boss.IsSafelyFacing(Me) && boss.Distance < 12), 7, viscosFluidStalkerId);

            return new PrioritySelector(
                ctx =>
                {
                    _pickupSwarmers = ObjectManager.GetObjectsOfType<WoWUnit>().Count(u => u.Combat && u != boss && u.IsTargetingMyPartyMember) >= 5;
                    return boss = ctx as WoWUnit;
                },
                new Decorator(
                    ctx => Me.IsTank() && boss.CastingSpellId == frenziedAssaultId,
                    new PrioritySelector(
                // back away from boss so he kills the adds with his frontal attack.
                        new Decorator(
                            ctx => (tankStepOutOfMeleeRangePoint = GetPointInFrontOfUnitAndOusideMeleeRange(boss, 1)) != WoWPoint.Zero,
                            new PrioritySelector(
                                new Decorator(
                                    ctx => Me.Location.Distance(tankStepOutOfMeleeRangePoint) <= Navigator.PathPrecision,
                                    new Action(ctx => Navigator.PlayerMover.MoveTowards(tankStepOutOfMeleeRangePoint))),
                                new Decorator(
                                    ctx => Me.Location.Distance(tankStepOutOfMeleeRangePoint) > Navigator.PathPrecision,
                                    new Action(ctx => Navigator.GetRunStatusFromMoveResult(Navigator.MoveTo(tankStepOutOfMeleeRangePoint)))))),
                // bot can't move to the front we just sidestep.
                        ScriptHelpers.CreateAvoidUnitAnglesBehavior(
                            ctx => tankStepOutOfMeleeRangePoint == WoWPoint.Zero && boss.Distance < 12, () => boss, new ScriptHelpers.AngleSpan(0, 180)))),
                ScriptHelpers.CreateAvoidUnitAnglesBehavior(ctx => !Me.IsTank() && boss.Distance < 12, () => boss, new ScriptHelpers.AngleSpan(0, 180)));
        }

        private WoWPoint GetPointInFrontOfUnitAndOusideMeleeRange(WoWUnit unit, float distancePastMeleeRange)
        {
            var facing = WoWMathHelper.NormalizeRadian(unit.Rotation);
            var point = unit.Location.RayCast(facing, 12 + distancePastMeleeRange);
            // set the Z coord to mesh level.
            var zList = Navigator.FindHeights(point.X, point.Y);
            if (zList != null && zList.Count > 0)
            {
                point.Z = zList.OrderBy(z => Math.Abs(z - point.Z)).FirstOrDefault();
                if (Navigator.CanNavigateFully(Me.Location, point))
                    return point;
            }
            return WoWPoint.Zero;
        }

        [EncounterHandler(56877, "Raigonn", BossRange = 200)]
        public Composite RaigonnEncounter()
        {
            WoWUnit boss = null;
            WoWUnit artillery = null;
            const uint artilleryId = 59819;
            const uint engulfingWindsId = 56928;
            const int vulnerabilityId = 111682;

            var leftSeatLoc = new WoWPoint(1.5, -3, 0.25);
            var rightSeatLoc = new WoWPoint(1.5, 3, 0.25);
            const float rightSeatCorrectFacing = 2.657843f;
            const float leftSeatCorrectFacing = 0.645875f;
            var roomCenterLoc = new WoWPoint(956.2532, 2275.753, 296.1056);
            var gateLoc = new WoWPoint(959.0933, 2227.044, 296.1056);

            AddAvoidObject(ctx => Me.HasAura("Fixate"), () => roomCenterLoc, 80, 40);
            AddAvoidObject(ctx => Me.TransportGuid == 0, o => Me.IsMoving && Me.IsRange() ? 10 : 5, engulfingWindsId);

            return new PrioritySelector(
                ctx => boss = ctx as WoWUnit,
                new Decorator(
                    ctx =>
                    boss.HasAura("Impervious Carapace") && Me.IsDps() && Me.TransportGuid != boss.Guid && Me.PartyMembers.Count(p => p.TransportGuid == boss.Guid) < 2 &&
                    (boss.Location.DistanceSqr(gateLoc) <= 50 * 50 || Targeting.Instance.IsEmpty()) &&
                    ObjectManager.GetObjectsOfType<WoWUnit>().Any(u => u.Entry == WeakSpotId && u.HasAura(vulnerabilityId)),
                    new PrioritySelector(
                        ctx =>
                        artillery =
                        ObjectManager.GetObjectsOfType<WoWUnit>()
                                     .OrderBy(u => u.DistanceSqr)
                                     .FirstOrDefault(
                                         u =>
                                         u.Entry == artilleryId &&
                                         !ObjectManager.GetObjectsOfType<WoWUnit>().Any(w => w.Entry == engulfingWindsId && u.Location.DistanceSqr(w.Location) < 12 * 12)),
                        new Decorator(ctx => Me.Mounted, new Action(ctx => Mount.Dismount())),
                        new Decorator(ctx => artillery != null, ScriptHelpers.CreateInteractWithObject(() => artillery, 0, true)))),
                // if stuck in an artillery then exit 
                new Decorator(ctx => Me.TransportGuid > 0 && Me.Transport.Entry == artilleryId, new Action(ctx => Lua.DoString("VehicleExit()"))),
                // make sure we're facing the weak spot.
                new Decorator( ctx => Me.CurrentTargetGuid != 0 && Me.CurrentTarget.Entry == WeakSpotId && !Me.IsSafelyFacing(Me.CurrentTarget), 
                    new Sequence(
                        new ActionLogger("Facing the WeakSpot"),
                        new Action(ctx => SetFacing(Me.CurrentTarget.Location)))),
                ScriptHelpers.CreateDispellParty("Screeching Swarm", ScriptHelpers.PartyDispellType.Magic));
        }

        RunStatus FaceWeakSpot(float direction)
        {
            Me.SetFacing(direction);
            return RunStatus.Failure;
        }
        #region Elevator

        private Composite CreateFollowerElevatorBehavior()
        {
            return new Decorator(
                ctx => !StyxWoW.Me.IsTank(),
                new Action(
                    ctx =>
                    {
                        var tankI = StyxWoW.Me.GroupInfo.RaidMembers.FirstOrDefault(p => p.HasRole(WoWPartyMember.GroupRole.Tank));

                        if (tankI != null)
                        {
                            var tank = tankI.ToPlayer();
                            var myFloorLevel = FloorLevel(Me.Location);
                            var tankLoc = tank != null ? tank.Location : tankI.Location3D;
                            var tankLevel = FloorLevel(tankLoc);
                            var elevatorRestingZ = myFloorLevel == 1 ? ElevatorBottomZ : ElevatorTopZ;
                            var elevatorBoardLoc = myFloorLevel == 2 ? _elevatorTopBoardLoc : _elevatorBottomBoardLoc;
                            var elevatorWaitLoc = myFloorLevel == 2 ? _elevatorTopWaitLoc : _elevatorBottomWaitLoc;

                            // do we need to get on a lift?
                            if (IsOnLift(tank) && !IsOnLift(Me) && tankLevel == myFloorLevel)
                            {
                                var ele = ObjectManager.GetObjectsOfType<WoWGameObject>().FirstOrDefault(g => g.Entry == ElevatorId);
                                bool elevatorIsReadyToBoard = ele != null && Math.Abs(ele.Z - elevatorRestingZ) <= 0.5f;
                                if (elevatorIsReadyToBoard)
                                {
                                    if (Me.Location.DistanceSqr(elevatorBoardLoc) > 3 * 3)
                                    {
                                        Logger.Write("[Elevator Manager] Boarding Elevator");
                                        Navigator.PlayerMover.MoveTowards(elevatorBoardLoc);
                                    }
                                    else
                                    {
                                        Logger.Write("[Elevator Manager] Jumping");
                                        WoWMovement.Move(WoWMovement.MovementDirection.JumpAscend);
                                        WoWMovement.MoveStop(WoWMovement.MovementDirection.JumpAscend);
                                    }
                                }
                                return RunStatus.Success;
                            }

                            // do we need to get off lift?
                            if (IsOnLift(Me))
                            {
                                var ele = ObjectManager.GetObjectsOfType<WoWGameObject>().FirstOrDefault(g => g.Entry == ElevatorId);
                                bool elevatorInUse = ele != null && Math.Abs(ele.Z - ElevatorBottomZ) > 0.5 && Math.Abs(ele.Z - ElevatorTopZ) > 0.5;
                                if (elevatorInUse)
                                    return RunStatus.Success;
                            }
                        }
                        return RunStatus.Failure;
                    }));
        }

        public bool ElevatorBehavior(WoWPoint destination)
        {
            if (AvoidanceManager.IsRunningOutOfAvoid)
                return false;
            var myloc = StyxWoW.Me.Location;

            var myFloorLevel = FloorLevel(myloc);
            var destinationFloorLevel = FloorLevel(destination);

            if (myFloorLevel != destinationFloorLevel)
            {
                var ele = ObjectManager.GetObjectsOfType<WoWGameObject>().FirstOrDefault(g => g.Entry == ElevatorId);

                var elevatorRestingZ = myFloorLevel == 1 ? ElevatorBottomZ : ElevatorTopZ;
                var elevatorBoardLoc = destinationFloorLevel == 1 ? _elevatorTopBoardLoc : _elevatorBottomBoardLoc;
                var elevatorWaitLoc = destinationFloorLevel == 1 ? _elevatorTopWaitLoc : _elevatorBottomWaitLoc;
                var lever = ObjectManager.GetObjectsOfType<WoWGameObject>().Where(o => o.Entry == 211284).OrderBy(o => o.DistanceSqr).FirstOrDefault();
                bool elevatorIsReadyToBoard = ele != null && Math.Abs(ele.Z - elevatorRestingZ) <= 0.5f;
                bool elevatorInUse = ele != null && Math.Abs(ele.Z - ElevatorBottomZ) > 0.5 && Math.Abs(ele.Z - ElevatorTopZ) > 0.5;

                // do nothing when taking elevator up.
                if (ele != null && Me.TransportGuid == ele.Guid && elevatorInUse)
                    return true;

                // move to the lever loc
                if ((ele == null || (myloc.DistanceSqr(elevatorBoardLoc) > 20 * 20 || !elevatorIsReadyToBoard && myloc.DistanceSqr(elevatorWaitLoc) > 4 * 4)) &&
                    Me.TransportGuid == 0)
                {
                    Logger.Write("[Elevator Manager] Moving To Elevator");
                    var moveResult = Navigator.MoveTo(elevatorWaitLoc);
                    return moveResult != MoveResult.Failed && moveResult != MoveResult.PathGenerationFailed;
                }
                // use lever to bring elevator to our floor level.
                if (!elevatorIsReadyToBoard && !elevatorInUse && myloc.DistanceSqr(elevatorWaitLoc) <= 20 * 20 && Me.TransportGuid == 0 && lever != null && lever.CanUse())
                {
                    if (myloc.DistanceSqr(elevatorWaitLoc) > 4 * 4)
                    {
                        Navigator.MoveTo(ele.Location);
                        return true;
                    }
                    Logger.Write("[Elevator Manager] Bringing Elevator to my level");
                    lever.Interact();
                }
                // get onboard of elevator.
                if (elevatorIsReadyToBoard && myloc.DistanceSqr(elevatorBoardLoc) > 2 * 2 && Me.TransportGuid == 0)
                {
                    Logger.Write("[Elevator Manager] Boarding Elevator");
                    // avoid getting stuck on lever
                    Navigator.PlayerMover.MoveTowards(elevatorBoardLoc);
                }
                else if (elevatorIsReadyToBoard && myloc.DistanceSqr(elevatorBoardLoc) <= 2 * 2 && Me.TransportGuid == 0)
                {
                    Logger.Write("[Elevator Manager] Jumping");
                    WoWMovement.Move(WoWMovement.MovementDirection.JumpAscend);
                    WoWMovement.MoveStop(WoWMovement.MovementDirection.JumpAscend);
                }
                if (elevatorIsReadyToBoard && Me.RaidMembers.Where(r => FloorLevel(r.Location) == myFloorLevel).All(r => r.TransportGuid == ele.Guid) && lever != null &&
                    lever.CanUse())
                {
                    if (!lever.CanUseNow())
                        Navigator.PlayerMover.MoveTowards(WoWMathHelper.CalculatePointFrom(Me.Location, lever.Location, lever.InteractRange - 1));
                    else if (Me.IsMoving)
                        WoWMovement.MoveStop();
                    Logger.Write("[Elevator Manager] Using Lever");
                    lever.Interact();
                }
                return true;
            }

            // exit elevator
            var transport = Me.TransportGuid > 0 ? Me.Transport : null;
            if (transport != null && transport.Entry == ElevatorId)
            {
                var elevatorExitZ = destinationFloorLevel == 1 ? ElevatorBottomZ : ElevatorTopZ;
                var elevatorExitLoc = destinationFloorLevel == 1 ? _elevatorBottomWaitLoc : _elevatorTopWaitLoc;
                bool elevatorIsReadyToExit = Math.Abs(transport.Z - elevatorExitZ) <= 0.5f;
                if (!elevatorIsReadyToExit || myFloorLevel != destinationFloorLevel)
                {
                    return true;
                }
            }

            return false;
        }


        private bool IsOnLift(WoWPlayer player)
        {
            return player != null && player.TransportGuid > 0 && player.Transport.Entry == ElevatorId;
        }

        private int FloorLevel(WoWPoint loc)
        {
            return loc.Z > 425 && loc.DistanceSqr(_gadokRoomLoc) <= 75 * 75 ? 2 : 1;
        }

        // ToDO remove this once facing gets fixed in HBCore.
        static void SetFacing(WoWPoint otherPoint)
        {
            var activeMover = WoWMovement.ActiveMover;
            if (activeMover == null || !activeMover.IsValid) return;
            var activeMoverLoc = activeMover.Location;
            var neededFacing = WoWMathHelper.CalculateNeededFacing(activeMoverLoc, otherPoint);
            var activeMoverTransport = activeMover.Transport;
            if (activeMoverTransport != null)
            {
                Matrix mat = activeMoverTransport.GetWorldMatrix();
                Matrix.Invert(ref mat, out mat);
                Tripper.Tools.Math.Vector3 nStart = activeMoverLoc;
                Tripper.Tools.Math.Vector3 nEnd = otherPoint;
                Tripper.Tools.Math.Vector3.Transform(ref nStart, ref mat, out nStart);
                Tripper.Tools.Math.Vector3.Transform(ref nEnd, ref mat, out nEnd);
                var r = WoWMathHelper.CalculateNeededFacing(nStart, nEnd);
                neededFacing = WoWMathHelper.NormalizeRadian(neededFacing + (neededFacing - r));
            }
            WoWMovement.ClickToMove(0L, WoWPoint.Empty, neededFacing + 1E-06f, WoWMovement.ClickToMoveType.Face);
        }
        #endregion
    }

    #endregion

    #region Heroic Difficulty

    public class GateOfTheSettingSunHeroic : GateOfTheSettingSun
    {
        #region Overrides of Dungeon

        public override uint DungeonId
        {
            get { return 471; }
        }

        #endregion
    }

    #endregion
}